package jmine.tec.hydra.services.receive;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import jmine.tec.di.annotation.Injected;
import jmine.tec.hydra.HydraController;
import jmine.tec.hydra.HydraMessages;
import jmine.tec.hydra.api.HydraRequest;
import jmine.tec.hydra.codec.Encoder;
import jmine.tec.hydra.domain.HydraSystem;
import jmine.tec.hydra.domain.message.HydraConversation;
import jmine.tec.hydra.exception.HydraException;
import jmine.tec.hydra.script.RenderMessageScriptContext;
import jmine.tec.script.ScriptExecutorException;
import jmine.tec.script.impl.velocity.VelocityScriptExecutor;
import jmine.tec.script.persistent.domain.Script;
import jmine.tec.services.annotations.Execution;
import jmine.tec.services.annotations.Input;
import jmine.tec.services.annotations.Output;
import jmine.tec.services.annotations.ServiceImplementor;
import jmine.tec.services.annotations.WithRegex;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.ServiceProperties;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Servico que simula o recebimento de mensagem.
 * 
 * @author piercio
 */
@ServiceImplementor(action = ActionsEnum.EXECUTAR)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class ReceiveMessageService {

    public static final String IDENTIFICADOR = "Identificador";

    public static final String CONVERSATION = "HydraConversation";

    public static final String MESSAGE = "Mensagem";

    public static final String TEMPLATE = "Template";

    public static final String REGEX_PARAM = "Parametro (\\w+)";

    public static final String REGEX_RECEBIMENTO_PARAM = "Recebimento Parametro (\\w+)";

    public static final String SYSTEM = "Sistema";

    public static final String ENCODING = "Encoding";

    // Injected
    private HydraController controller;

    private ServiceProperties serviceProperties;

    // Obrigatórios
    private HydraSystem system;

    // Opcionais
    private Script template;

    private Map<String, String> parametros;

    private Map<String, String> parametrosRecebimento;

    private HydraConversation conversation;

    private String encoding; // default UTF-8

    /**
     * Executa o servico.
     * 
     * @return a mensagem de integração recebida.
     * @throws UnsupportedEncodingException se o encoding da mensagem não for UTF-8
     */
    @Execution
    @Output(propertyName = IDENTIFICADOR)
    public HydraRequest execute() throws UnsupportedEncodingException {
        return this.controller.receive(this.system, this.getInput(), this.parametrosRecebimento);
    }

    /**
     * Obtém a mensagem que deve ser recebida pelo Hydra
     * 
     * @return InputStream
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    private InputStream getInput() throws UnsupportedEncodingException {
        try {
            if (this.serviceProperties.containsKey(MESSAGE)) {
                return new ByteArrayInputStream(this.serviceProperties.getRaw(MESSAGE).getBytes(this.encoding));
            } else {
                return this.template.createContext(RenderMessageScriptContext.class).prepare(this.getParametros()).dispatchExecution()
                        .getStream().getAsInputStream();
            }
        } catch (ScriptExecutorException e) {
            throw new HydraException(HydraMessages.VELOCITY_TEMPLATE.create(), e);
        } catch (IOException e) {
            throw new HydraException(HydraMessages.VELOCITY_TEMPLATE.create(), e);
        }
    }

    /**
     * @param controller o valor para o campo controller
     */
    @Injected
    public void setController(HydraController controller) {
        this.controller = controller;
    }

    /**
     * @param serviceProperties the serviceProperties to set
     */
    @Injected
    public void setServiceProperties(ServiceProperties serviceProperties) {
        this.serviceProperties = serviceProperties;
    }

    /**
     * @param system o valor para o campo system
     */
    @Input(fieldName = SYSTEM)
    public void setSystem(HydraSystem system) {
        this.system = system;
    }

    /**
     * @param mensagemRecebida mensagem recebida.
     */
    @Input(fieldName = MESSAGE, required = false)
    public void setMensagemRecebida(String mensagemRecebida) {
    }

    /**
     * @param template the template to set
     */
    @Input(fieldName = TEMPLATE, required = false)
    public void setTemplate(Script template) {
        this.template = template;
    }

    /**
     * @param conversation o valor para o campo conversation
     */
    @Input(fieldName = CONVERSATION, required = false)
    public void setConversation(HydraConversation conversation) {
        this.conversation = conversation;
    }

    /**
     * @param parametros the parametros to set
     */
    @WithRegex(regex = REGEX_PARAM, groupIndex = 1, required = false)
    public void setParametros(Map<String, String> parametros) {
        this.parametros = parametros;
    }

    /**
     * @param parametrosRecebimento the parametrosRecebimento to set
     */
    @WithRegex(regex = REGEX_RECEBIMENTO_PARAM, groupIndex = 1, required = false)
    public void setParametrosRecebimento(Map<String, String> parametrosRecebimento) {
        this.parametrosRecebimento = parametrosRecebimento;
    }

    /**
     * @param encoding the encoding to set
     */
    @Input(fieldName = ENCODING, defaultValue = "UTF-8")
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /**
     * Monta o mapa de parâmetros a serem passados para a template velocity.
     * 
     * @return mapa de parâmetros para o velocity
     */
    private Map<String, Object> getParametros() {
        Map<String, Object> p = new HashMap<String, Object>();
        for (String key : this.parametros.keySet()) {
            p.put(key, this.serviceProperties.getRaw(String.format("Parametro %s", key)));
        }
        if (this.conversation != null) {
            p.put(Encoder.IDENTIFICATION, this.conversation.getMnemonico());
        }
        p.put(VelocityScriptExecutor.TEMPLATE_ENCODING, this.encoding);
        return p;
    }
}
